From: Jan Beulich Date: Thu, 2 May 2013 14:35:50 +0000 (+0200) Subject: x86: make new_guest_cr3() preemptible X-Git-Tag: archive/raspbian/4.8.0-1+rpi1~1^2~6932 X-Git-Url: https://dgit.raspbian.org/%22http:/www.example.com/cgi/%22https://%22%22/%22http:/www.example.com/cgi/%22https:/%22%22?a=commitdiff_plain;h=e2e6b7b627fec0d7a769ab46441f2985ebccbf04;p=xen.git x86: make new_guest_cr3() preemptible ... as it may take significant amounts of time. This is part of CVE-2013-1918 / XSA-45. Signed-off-by: Jan Beulich Acked-by: Tim Deegan --- diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c index 9a076e9d36..e43a698791 100644 --- a/xen/arch/x86/mm.c +++ b/xen/arch/x86/mm.c @@ -2656,7 +2656,7 @@ int new_guest_cr3(unsigned long mfn) { struct vcpu *curr = current; struct domain *d = curr->domain; - int okay; + int rc; unsigned long old_base_mfn; if ( is_pv_32on64_domain(d) ) @@ -2664,39 +2664,64 @@ int new_guest_cr3(unsigned long mfn) unsigned long gt_mfn = pagetable_get_pfn(curr->arch.guest_table); l4_pgentry_t *pl4e = map_domain_page(gt_mfn); - okay = paging_mode_refcounts(d) - ? 0 /* Old code was broken, but what should it be? */ - : mod_l4_entry( + rc = paging_mode_refcounts(d) + ? -EINVAL /* Old code was broken, but what should it be? */ + : mod_l4_entry( pl4e, l4e_from_pfn( mfn, (_PAGE_PRESENT|_PAGE_RW|_PAGE_USER|_PAGE_ACCESSED)), - gt_mfn, 0, 0, curr) == 0; + gt_mfn, 0, 1, curr); unmap_domain_page(pl4e); - if ( unlikely(!okay) ) + switch ( rc ) { + case 0: + break; + case -EINTR: + case -EAGAIN: + return -EAGAIN; + default: MEM_LOG("Error while installing new compat baseptr %lx", mfn); - return 0; + return rc; } invalidate_shadow_ldt(curr, 0); write_ptbase(curr); - return 1; + return 0; } - okay = paging_mode_refcounts(d) - ? get_page_from_pagenr(mfn, d) - : !get_page_and_type_from_pagenr(mfn, PGT_root_page_table, d, 0, 0); - if ( unlikely(!okay) ) + rc = put_old_guest_table(curr); + if ( unlikely(rc) ) + return rc; + + old_base_mfn = pagetable_get_pfn(curr->arch.guest_table); + /* + * This is particularly important when getting restarted after the + * previous attempt got preempted in the put-old-MFN phase. + */ + if ( old_base_mfn == mfn ) { - MEM_LOG("Error while installing new baseptr %lx", mfn); + write_ptbase(curr); return 0; } - invalidate_shadow_ldt(curr, 0); + rc = paging_mode_refcounts(d) + ? (get_page_from_pagenr(mfn, d) ? 0 : -EINVAL) + : get_page_and_type_from_pagenr(mfn, PGT_root_page_table, d, 0, 1); + switch ( rc ) + { + case 0: + break; + case -EINTR: + case -EAGAIN: + return -EAGAIN; + default: + MEM_LOG("Error while installing new baseptr %lx", mfn); + return rc; + } - old_base_mfn = pagetable_get_pfn(curr->arch.guest_table); + invalidate_shadow_ldt(curr, 0); curr->arch.guest_table = pagetable_from_pfn(mfn); update_cr3(curr); @@ -2705,13 +2730,25 @@ int new_guest_cr3(unsigned long mfn) if ( likely(old_base_mfn != 0) ) { + struct page_info *page = mfn_to_page(old_base_mfn); + if ( paging_mode_refcounts(d) ) - put_page(mfn_to_page(old_base_mfn)); + put_page(page); else - put_page_and_type(mfn_to_page(old_base_mfn)); + switch ( rc = put_page_and_type_preemptible(page, 1) ) + { + case -EINTR: + rc = -EAGAIN; + case -EAGAIN: + curr->arch.old_guest_table = page; + break; + default: + BUG_ON(rc); + break; + } } - return 1; + return rc; } static struct domain *get_pg_owner(domid_t domid) @@ -2982,8 +3019,13 @@ long do_mmuext_op( } case MMUEXT_NEW_BASEPTR: - okay = (!paging_mode_translate(d) - && new_guest_cr3(op.arg1.mfn)); + if ( paging_mode_translate(d) ) + okay = 0; + else + { + rc = new_guest_cr3(op.arg1.mfn); + okay = !rc; + } break; case MMUEXT_NEW_USER_BASEPTR: { diff --git a/xen/arch/x86/traps.c b/xen/arch/x86/traps.c index d36eddd229..4de9313c74 100644 --- a/xen/arch/x86/traps.c +++ b/xen/arch/x86/traps.c @@ -2322,12 +2322,23 @@ static int emulate_privileged_op(struct cpu_user_regs *regs) gfn = !is_pv_32on64_vcpu(v) ? xen_cr3_to_pfn(*reg) : compat_cr3_to_pfn(*reg); page = get_page_from_gfn(v->domain, gfn, NULL, P2M_ALLOC); - rc = page ? new_guest_cr3(page_to_mfn(page)) : 0; if ( page ) + { + rc = new_guest_cr3(page_to_mfn(page)); put_page(page); + } + else + rc = -EINVAL; domain_unlock(v->domain); - if ( rc == 0 ) /* not okay */ + switch ( rc ) + { + case 0: + break; + case -EAGAIN: /* retry after preemption */ + goto skip; + default: /* not okay */ goto fail; + } break; }